"""
Core CLI commands for GCR Solver Manager.

Simplified interface focusing on essential functionality:
- run: Run solver locally (auto-build if needed)
- run-remote: Run solver on remote system
- list-runs: Show recent runs
- show-log: Show specific run logs
- sync: Sync with remote server
- stats: System statistics
"""

import click
import logging
import subprocess
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Optional

from ..core.simple_config import get_config
from ..test.simple_runner import SimpleTestRunner
from ..database.simple_db import SimpleDatabase
from ..logs.manager import LogManager
from ..extensions import initialize_extensions, get_registry, get_router
from .extension_commands import extension_commands

# Setup logging
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
logger = logging.getLogger(__name__)

# Global flag to track extension initialization
_extensions_initialized = False


def ensure_extensions_initialized():
    """Ensure extensions are initialized before processing commands."""
    global _extensions_initialized
    if not _extensions_initialized:
        try:
            logger.debug("Initializing GSM extension system...")
            initialize_extensions()
            _extensions_initialized = True
            logger.debug("Extension system initialized successfully")
        except Exception as e:
            logger.warning(f"Failed to initialize extension system: {e}")


def preprocess_show_log_args():
    """Preprocess arguments to handle -s without an ID."""
    import sys
    args = sys.argv[1:]
    
    # Check if -s or --show-log is present without an argument
    for i, arg in enumerate(args):
        if arg in ['-s', '--show-log']:
            # Check if next arg exists and is not another option
            if i + 1 >= len(args) or args[i + 1].startswith('-'):
                # Insert empty string as argument for -s/--show-log
                args.insert(i + 1, '')
                sys.argv[1:] = args
                break
    
    return args


@click.group(invoke_without_command=True, context_settings={'help_option_names': ['-h', '--help']})
@click.option('--config', type=click.Path(), help='Configuration file path')
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
@click.option('-lt', '--list', 'list_runs_flag', is_flag=True, help='List recent runs (equivalent to list-runs)')
@click.option('-n', '--count', type=int, default=10, help='Number of runs to show (used with -lt)')
@click.option('-s', '--show-log', type=str, metavar='ID', default='', help='Show log content for specific run ID. Defaults to latest run if no ID provided.')
@click.option('-lx', '--list-xml', is_flag=True, help='List registered XML configurations')
@click.option('--run-id', '-ri', type=int, metavar='XML_ID', help='Run XML configuration by ID')
@click.pass_context
def cli(ctx, config, verbose, list_runs_flag, count, show_log, list_xml, run_id):
    """GCR Solver Manager - Extensible solver execution and management."""
    ctx.ensure_object(dict)
    
    # Initialize extensions early
    ensure_extensions_initialized()
    
    if verbose:
        logging.getLogger().setLevel(logging.DEBUG)
    
    if config:
        from ..core.simple_config import load_config
        load_config(config)
    
    # Handle -l/--list flag by invoking list-runs functionality directly
    if list_runs_flag:
        try:
            config = get_config()
            db = SimpleDatabase(config.get('database.path'))
            
            # Get recent runs using the specified count
            runs = db.get_latest(count)
            
            if not runs:
                click.echo("No runs found")
                return
            
            # Display runs in the same format as list-runs
            click.echo("Recent runs:")
            click.echo("-" * 80)
            for run in runs:
                status_symbol = "✅" if run['status'] == 'success' else "❌" if run['status'] == 'failed' else "🔄"
                duration = run.get('duration', 0)
                duration_str = f"{duration:.1f}s" if duration else "N/A"
                created_at = run.get('created_at', 'N/A')
                if isinstance(created_at, str):
                    from datetime import datetime
                    try:
                        dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
                        created_at = dt.strftime('%m-%d %H:%M')
                    except:
                        created_at = 'N/A'
                
                solver = run.get('solver', 'unknown')
                gpu = run.get('gpu') or 'cpu'
                run_type = run.get('type', 'test')
                
                click.echo(f"{status_symbol} #{run['id']:3d} | {created_at} | {duration_str:>6} | {solver:>10} | {gpu:>4} | {run_type}")
                
        except Exception as e:
            click.echo(f"❌ Error listing runs: {e}")
        return
    
    # Handle -s/--show-log flag when explicitly provided (even if empty)
    # We need to check if the option was actually used
    import sys
    if any(arg in ['-s', '--show-log'] for arg in sys.argv):
        try:
            config = get_config()
            db = SimpleDatabase(config.get('database.path'))
            
            # If no ID provided, default to latest run
            if not show_log or show_log.strip() == '':
                runs = db.get_latest(1)
                if runs:
                    run_id = runs[0]['id']
                    click.echo(f"No ID provided, using latest run ID: {run_id}")
                else:
                    click.echo("❌ No runs found to show")
                    return
            else:
                try:
                    run_id = int(show_log)
                except ValueError:
                    click.echo(f"❌ Invalid run ID: '{show_log}'. Must be a number.")
                    return
            
            # Get run data
            run_data = db.get_by_id(run_id)
            if not run_data:
                click.echo(f"❌ Run ID {run_id} not found")
                return
            
            # Display run info
            status_symbol = "✅" if run_data['status'] == 'success' else "❌" if run_data['status'] == 'failed' else "🔄"
            duration = run_data.get('duration', 0)
            duration_str = f"{duration:.1f}s" if duration else "N/A"
            created_at = run_data.get('created_at', 'N/A')
            if isinstance(created_at, str):
                from datetime import datetime
                try:
                    dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
                    created_at = dt.strftime('%Y-%m-%d %H:%M:%S')
                except:
                    created_at = 'N/A'
            
            solver = run_data.get('solver', 'unknown')
            gpu = run_data.get('gpu') or 'cpu'
            run_type = run_data.get('type', 'test')
            
            click.echo(f"{status_symbol} #{run_data['id']} | {solver} | {gpu} | {run_type}")
            click.echo(f"Started: {created_at}")
            click.echo(f"Status: {run_data['status'].upper()}")
            click.echo(f"Duration: {duration_str}")
            
            # Show log file content if available
            log_path = run_data.get('log_path')
            if log_path:
                click.echo(f"Log file: {log_path}")
                click.echo()
                
                # Try to find and display log content
                log_file_path = Path(log_path)
                
                # Convert to absolute path if relative
                if not log_file_path.is_absolute():
                    source_dir = config.get('build.source_dir', '/home/linden/code/work/Helmholtz/gcr-nccl')
                    possible_paths = [
                        Path(source_dir) / 'gcr-solver-manager' / log_path,
                        Path(source_dir) / log_path,
                        Path('/home/linden/.cache/gsm') / log_path
                    ]
                    for possible_path in possible_paths:
                        if possible_path.exists():
                            log_file_path = possible_path
                            break
                
                if log_file_path.exists():
                    click.echo("Log content:")
                    click.echo("-" * 40)
                    with open(log_file_path, 'r') as f:
                        click.echo(f.read())
                else:
                    click.echo(f"❌ Log file not found: {log_path}")
            else:
                click.echo("❌ No log file path available for this run")
                
        except Exception as e:
            click.echo(f"❌ Error showing log: {e}")
        return
    
    # Handle --list-xml flag
    if list_xml:
        try:
            from ..config.xml_manager import get_xml_manager
            xml_manager = get_xml_manager()
            
            configs = xml_manager.list_xmls(count)
            if not configs:
                click.echo("No XML configurations found")
                return
            
            click.echo("Registered XML configurations:")
            click.echo("-" * 80)
            click.echo(f"{'ID':>3} | {'Name':<20} | {'Binary':<25} | {'MPI':<3} | {'Last Used':<16}")
            click.echo("-" * 80)
            
            for config in configs:
                binary_name = Path(config['binary_path']).name if config['binary_path'] else 'N/A'
                if len(binary_name) > 25:
                    binary_name = binary_name[:22] + "..."
                
                last_used = config['last_used'] or 'Never'
                if len(last_used) > 16:
                    last_used = last_used[:13] + "..."
                
                click.echo(f"{config['id']:>3} | {config['name'][:20]:<20} | {binary_name:<25} | {config['mpi_processes'] or 1:<3} | {last_used:<16}")
            
            click.echo("-" * 80)
            click.echo(f"Total: {len(configs)} configurations")
            click.echo("💡 Use 'gsm --run-id <ID>' or 'gsm -ri <ID>' to run a configuration")
            
        except Exception as e:
            click.echo(f"❌ Error listing XML configs: {e}")
        return
    
    # Handle --run-id/-ri flag
    if run_id is not None:
        try:
            from ..config.xml_manager import get_xml_manager
            xml_manager = get_xml_manager()
            
            click.echo(f"🚀 Running XML configuration ID: {run_id}")
            success, output = xml_manager.run_xml_by_id(run_id)
            
            if success:
                click.echo("✅ XML configuration executed successfully!")
            else:
                click.echo("❌ XML configuration execution failed")
            
            click.echo("\nExecution output:")
            click.echo("-" * 40)
            click.echo(output)
            
        except Exception as e:
            click.echo(f"❌ Error running XML config ID {run_id}: {e}")
        return
    
    # If no command and no flags, show help
    import sys
    show_log_used = any(arg in ['-s', '--show-log'] for arg in sys.argv)
    if ctx.invoked_subcommand is None and not list_runs_flag and not show_log_used and not list_xml and run_id is None:
        click.echo(ctx.get_help())


# Register extension management commands
cli.add_command(extension_commands)


def register_extension_commands():
    """Register commands from all loaded extensions."""
    try:
        registry = get_registry()
        extension_commands = registry.get_extension_commands()
        
        for cmd_name, cmd_obj in extension_commands.items():
            # Add the command to the CLI
            cli.add_command(cmd_obj, name=cmd_name)
            logger.debug(f"Registered extension command: {cmd_name}")
            
    except Exception as e:
        logger.warning(f"Failed to register extension commands: {e}")


def main():
    """Entry point that preprocesses arguments and runs the CLI."""
    preprocess_show_log_args()
    
    # Initialize extensions and register their commands
    ensure_extensions_initialized()
    register_extension_commands()
    
    cli()


if __name__ == '__main__':
    main()


@cli.command()
@click.argument('config_file', required=False)
@click.option('--solver', multiple=True, type=click.Choice(['gcr', 'ca-gcr', 'gmres', 'bca-gmres', 'all']), 
              help='Solver(s) to run. Use "all" to run all solvers, or specify multiple: --solver=gcr --solver=gmres')
@click.option('--gpu', type=click.Choice(['cuda', 'hip', 'cpu']), default='cpu', help='GPU type')
@click.option('--nproc', default=4, help='Number of MPI processes')
@click.option('--resolution', default=1.0, type=float, help='Grid resolution')
@click.option('--debug', is_flag=True, help='Run in debug mode')
def run(config_file, solver, gpu, nproc, resolution, debug):
    """Run solver(s) locally (binary execution only).
    
    Usage:
        gsm run config.xml                              # Run with XML config
        gsm run --solver=gmres --gpu=cuda --debug       # Run with CLI options
    """
    try:
        # Check if using XML configuration
        if config_file:
            return run_with_xml_config(config_file)
        
        # Handle solver selection for CLI mode
        if not solver:
            click.echo("❌ No solver specified. Use --solver=gcr or --solver=all, or provide an XML config file")
            return
        
        # Convert solver tuple to list and handle 'all' option
        solvers = list(solver)
        if 'all' in solvers:
            solvers = ['gcr', 'ca-gcr', 'gmres', 'bca-gmres']
        
        # Remove duplicates while preserving order
        solvers = list(dict.fromkeys(solvers))
        
        runner = SimpleTestRunner()
        params = {
            'nproc': nproc,
            'resolution': resolution,
            'debug': debug
        }
        
        # Run solvers
        if len(solvers) == 1:
            # Single solver - use existing behavior
            success, run_id = runner.run(solvers[0], gpu, params)
            
            if success:
                click.echo(f"✅ Run completed successfully (run_id: {run_id})")
                
                # Auto-run compare analysis for debug mode
                if debug:
                    click.echo(f"🔍 Debug mode detected - running automatic analysis for run_id: {run_id}")
                    try:
                        from ..core.simple_config import get_config
                        from ..database.simple_db import SimpleDatabase
                        from pathlib import Path
                        
                        config = get_config()
                        db = SimpleDatabase(config.get('database.path'))
                        
                        # Get run data
                        run_data = db.get_by_id(run_id)
                        if run_data and run_data['type'] == 'test':
                            log_path = Path(run_data['log_path'])
                            
                            # Convert to absolute path if relative
                            if not log_path.is_absolute():
                                from pathlib import Path as PathLib
                                source_dir = config.get('build.source_dir', '/home/linden/code/work/Helmholtz/gcr-nccl')
                                
                                # Try multiple possible locations for log files
                                possible_paths = [
                                    PathLib(source_dir) / 'gcr-solver-manager' / log_path,
                                    PathLib(source_dir) / log_path,
                                    PathLib('/home/linden/.cache/gsm') / log_path
                                ]
                                
                                for possible_path in possible_paths:
                                    if possible_path.exists():
                                        log_path = possible_path
                                        break
                            
                            if log_path.exists():
                                from ..analysis import analyze_log_file
                                
                                click.echo(f"📄 Analyzing log file: {log_path}")
                                try:
                                    result = analyze_log_file(str(log_path))
                                    click.echo(f"🎯 Analysis completed for debug run")
                                    # Display key analysis results
                                    if result and 'summary' in result:
                                        summary = result['summary']
                                        if 'total_tests' in summary:
                                            click.echo(f"📊 Found {summary['total_tests']} solver implementations")
                                        if 'performance_metrics' in result:
                                            click.echo(f"🚀 Performance data analyzed")
                                except Exception as analysis_error:
                                    click.echo(f"⚠️ Analysis failed: {analysis_error}")
                            else:
                                click.echo(f"⚠️ Log file not found: {log_path}")
                        else:
                            click.echo(f"⚠️ Could not retrieve run data for analysis")
                            
                    except Exception as e:
                        click.echo(f"⚠️ Auto-analysis error: {e}")
                        
            else:
                click.echo(f"❌ Run failed (run_id: {run_id})")
        else:
            # Multiple solvers - run in batch
            click.echo(f"🔄 Running {len(solvers)} solvers: {', '.join(solvers)}")
            
            results = []
            for i, solver_name in enumerate(solvers, 1):
                click.echo(f"\n[{i}/{len(solvers)}] Running {solver_name} solver...")
                
                try:
                    success, run_id = runner.run(solver_name, gpu, params)
                    results.append((solver_name, success, run_id))
                    
                    if success:
                        click.echo(f"✅ {solver_name} completed successfully (run_id: {run_id})")
                        
                        # Auto-run compare analysis for debug mode
                        if debug:
                            click.echo(f"🔍 Debug mode - running automatic analysis for {solver_name} (run_id: {run_id})")
                            try:
                                from ..analysis import analyze_log_file
                                from ..core.simple_config import get_config
                                from ..database.simple_db import SimpleDatabase
                                from pathlib import Path
                                
                                config = get_config()
                                db = SimpleDatabase(config.get('database.path'))
                                run_data = db.get_by_id(run_id)
                                
                                if run_data and run_data['type'] == 'test':
                                    log_path = Path(run_data['log_path'])
                                    if not log_path.is_absolute():
                                        source_dir = config.get('build.source_dir', '/home/linden/code/work/Helmholtz/gcr-nccl')
                                        possible_paths = [
                                            Path(source_dir) / 'gcr-solver-manager' / log_path,
                                            Path(source_dir) / log_path,
                                            Path('/home/linden/.cache/gsm') / log_path
                                        ]
                                        for possible_path in possible_paths:
                                            if possible_path.exists():
                                                log_path = possible_path
                                                break
                                    
                                    if log_path.exists():
                                        result = analyze_log_file(str(log_path))
                                        click.echo(f"🎯 Analysis completed for {solver_name}")
                                        # Brief summary for batch mode
                                        if result and 'summary' in result:
                                            summary = result['summary']
                                            if 'total_tests' in summary:
                                                click.echo(f"   📊 {summary['total_tests']} implementations found")
                                        
                            except Exception as analysis_error:
                                click.echo(f"⚠️ Auto-analysis failed for {solver_name}: {analysis_error}")
                                
                    else:
                        click.echo(f"❌ {solver_name} failed (run_id: {run_id})")
                        
                except Exception as e:
                    click.echo(f"❌ {solver_name} error: {e}")
                    results.append((solver_name, False, None))
            
            # Summary
            successful = sum(1 for _, success, _ in results if success)
            click.echo(f"\n📊 Batch Results: {successful}/{len(results)} solvers succeeded")
            
            for solver_name, success, run_id in results:
                status = "✅" if success else "❌"
                click.echo(f"  {status} {solver_name}: run_id={run_id}")
            
    except Exception as e:
        click.echo(f"❌ Run error: {e}")


@cli.command('run-remote')
@click.option('--solver', multiple=True, type=click.Choice(['gcr', 'ca-gcr', 'gmres', 'bca-gmres', 'all']),
              help='Solver(s) to run remotely. Use "all" to run all solvers, or specify multiple')
@click.option('--gpu', type=click.Choice(['cuda', 'hip', 'cpu']), default='hip', help='GPU type')
@click.option('--nproc', default=4, help='Number of MPI processes')
@click.option('--resolution', default=1.0, type=float, help='Grid resolution')
@click.option('--debug', is_flag=True, help='Run in debug mode')
@click.option('--hostname', help='SSH hostname from SSH config (overrides .env)')
@click.option('--workdir', help='Remote working directory (overrides config)')
def run_remote(solver, gpu, nproc, resolution, debug, hostname, workdir):
    """Run solver(s) on remote system via SSH (auto-build on remote)."""
    try:
        # Handle solver selection
        if not solver:
            click.echo("❌ No solver specified. Use --solver=gcr or --solver=all")
            return
        
        # Convert solver tuple to list and handle 'all' option
        solvers = list(solver)
        if 'all' in solvers:
            solvers = ['gcr', 'ca-gcr', 'gmres', 'bca-gmres']
        
        # Remove duplicates while preserving order
        solvers = list(dict.fromkeys(solvers))
        
        runner = SimpleTestRunner()
        params = {
            'nproc': nproc,
            'resolution': resolution,
            'debug': debug
        }
        
        remote_config = {}
        if hostname:
            remote_config['hostname'] = hostname
        if workdir:
            remote_config['workdir'] = workdir
        
        # Run solvers remotely
        if len(solvers) == 1:
            # Single solver - use existing behavior
            success, run_id = runner.run_remote_test(solvers[0], gpu, params, remote_config)
            
            if success:
                click.echo(f"✅ Remote run completed successfully (run_id: {run_id})")
            else:
                click.echo(f"❌ Remote run failed (run_id: {run_id})")
        else:
            # Multiple solvers - run in batch remotely
            click.echo(f"🔄 Running {len(solvers)} solvers remotely: {', '.join(solvers)}")
            
            results = []
            for i, solver_name in enumerate(solvers, 1):
                click.echo(f"\n[{i}/{len(solvers)}] Running {solver_name} solver remotely...")
                
                try:
                    success, run_id = runner.run_remote_test(solver_name, gpu, params, remote_config)
                    results.append((solver_name, success, run_id))
                    
                    if success:
                        click.echo(f"✅ {solver_name} completed successfully (run_id: {run_id})")
                    else:
                        click.echo(f"❌ {solver_name} failed (run_id: {run_id})")
                        
                except Exception as e:
                    click.echo(f"❌ {solver_name} error: {e}")
                    results.append((solver_name, False, None))
            
            # Summary
            successful = sum(1 for _, success, _ in results if success)
            click.echo(f"\n📊 Remote Batch Results: {successful}/{len(results)} solvers succeeded")
            
            for solver_name, success, run_id in results:
                status = "✅" if success else "❌"
                click.echo(f"  {status} {solver_name}: run_id={run_id}")
            
    except Exception as e:
        click.echo(f"❌ Remote run error: {e}")


@cli.command('list-runs')  
@click.option('-n', '--count', default=10, help='Number of runs to show')
@click.option('--type', type=click.Choice(['test', 'build']), help='Filter by run type')
@click.option('--solver', help='Filter by solver')
@click.option('--gpu', help='Filter by GPU type')
def list_runs(count, type, solver, gpu):
    """List recent runs."""
    try:
        config = get_config()
        db = SimpleDatabase(config.get('database.path'))
        runs = db.get_latest(count, type, solver, gpu)
        
        if not runs:
            click.echo("No runs found.")
            return
        
        click.echo(f"Recent runs ({len(runs)}):")
        for run in runs:
            status_emoji = "✅" if run['status'] == 'completed' else "❌" if run['status'] == 'failed' else "🔄"
            click.echo(f"    {run['id']:3d} | {run['type']:5s} | {run['solver']:8s} | {run['gpu']:4s} | {status_emoji} {run['status']:9s} | {run['created_at']}")
            
    except Exception as e:
        click.echo(f"❌ List runs error: {e}")



@cli.command('show-log')
@click.argument('run_id', type=int, required=False)
def show_log(run_id):
    """Show log content for a specific run."""
    try:
        log_manager = LogManager()
        
        # If no run_id provided, use the latest one
        if run_id is None:
            from ..core.simple_config import get_config
            from ..database.simple_db import SimpleDatabase
            
            config = get_config()
            db = SimpleDatabase(config.get('database.path'))
            
            # Get the latest run
            latest_runs = db.get_latest(n=1)
            if not latest_runs:
                click.echo("❌ No runs found")
                return
            
            run_id = latest_runs[0]['id']
            click.echo(f"No run ID provided, using latest run ID: {run_id}")
        
        log_content = log_manager.get_log_content(run_id)
        
        if not log_content:
            click.echo(f"No log found for run ID {run_id}")
            return
        
        click.echo(log_content)
        
    except Exception as e:
        click.echo(f"❌ Show log error: {e}")




@cli.command()
@click.argument('run_id', type=int)
@click.option('--output', '-o', help='Output file for analysis plots')
@click.option('--verbose', '-v', is_flag=True, help='Show detailed analysis output')
def compare(run_id, output, verbose):
    """Analyze test run using integrated solver analysis with improved auto-detection."""
    try:
        from ..analysis import analyze_log_file
        config = get_config()
        db = SimpleDatabase(config.get('database.path'))
        
        # Get run data
        run_data = db.get_by_id(run_id)
        if not run_data:
            click.echo(f"❌ Run ID {run_id} not found")
            return
        
        if run_data['type'] != 'test':
            click.echo(f"❌ Run ID {run_id} is not a test run (type: {run_data['type']})")
            return
        
        log_path = Path(run_data['log_path'])
        # Convert to absolute path if relative
        if not log_path.is_absolute():
            from pathlib import Path as PathLib
            config = get_config()
            source_dir = config.get('build.source_dir', '/home/linden/code/work/Helmholtz/gcr-nccl')
            
            # Try multiple possible locations for log files
            possible_paths = [
                PathLib(source_dir) / 'gcr-solver-manager' / log_path,  # GSM subdirectory
                PathLib(source_dir) / log_path,  # Main directory
                PathLib('/home/linden/.cache/gsm') / log_path  # Cache directory
            ]
            
            log_path = None
            for path in possible_paths:
                if path.exists():
                    log_path = path
                    break
            
            if not log_path:
                click.echo(f"❌ Log file not found in any of these locations:")
                for path in possible_paths:
                    click.echo(f"   - {path}")
                return
        
        if not log_path.exists():
            click.echo(f"❌ Log file not found: {log_path}")
            return
        
        click.echo(f"📊 Analyzing run {run_id} ({run_data['solver']}/{run_data['gpu']}) using integrated analysis...")
        click.echo(f"📄 Log file: {log_path}")
        
        # Perform analysis using integrated module
        results = analyze_log_file(str(log_path))
        
        if 'error' in results:
            click.echo(f"❌ Analysis failed: {results['error']}")
            return
        
        # Display results
        click.echo("✅ Analysis completed successfully!")
        click.echo("\n" + "="*50)
        click.echo("ANALYSIS RESULTS:")
        click.echo("="*50)
        
        # Show the formatted report
        click.echo(results['report'])
        
        # Show additional details if verbose
        if verbose:
            click.echo("\n" + "="*50)
            click.echo("DETAILED ANALYSIS:")
            click.echo("="*50)
            click.echo(f"Mode: {results['mode']}")
            
            if results['mode'] == 'debug':
                click.echo(f"Total entries: {results['total_entries']}")
                click.echo(f"Solver groups: {results['solver_groups']}")
                click.echo(f"Summary: {results['summary']}")
            else:
                click.echo(f"Residual data: {len(results.get('residual_data', {}))}")
        
        # Handle output file if requested
        if output:
            output_path = Path(output)
            try:
                with open(output_path, 'w') as f:
                    f.write(results['report'])
                click.echo(f"\n💾 Analysis saved to: {output_path}")
            except Exception as e:
                click.echo(f"⚠️  Could not save to {output_path}: {e}")
    
    except ImportError as e:
        click.echo(f"❌ Missing analysis dependencies: {e}")
        click.echo("Install matplotlib with: pip install matplotlib")
        return
    except Exception as e:
        click.echo(f"❌ Error: {e}")
        return


@cli.command()
@click.option('--logs-only', is_flag=True, help='Sync only log files')
@click.option('--verbose', '-v', is_flag=True, help='Show detailed output')
def sync(logs_only, verbose):
    """Synchronize repository and logs with remote server."""
    try:
        runner = SimpleTestRunner()
        
        if logs_only:
            success = runner.sync_remote_logs_only(verbose)
        else:
            # Full sync - would need to implement this
            click.echo("Full sync not yet implemented - use --logs-only")
            return
        
        if success:
            click.echo("✅ Synchronization completed successfully")
        else:
            click.echo("❌ Synchronization failed")
            
    except Exception as e:
        click.echo(f"❌ Sync error: {e}")


@cli.command()
def stats():
    """Show system statistics."""
    try:
        # Database stats only
        config = get_config()
        db = SimpleDatabase(config.get('database.path'))
        db_stats = db.get_statistics()
        
        click.echo("=== Database Statistics ===")
        click.echo(f"Total runs: {db_stats['overview']['total_runs']}")
        click.echo(f"Test runs: {db_stats['overview']['test_runs']}")
        
        if db_stats['solvers']:
            click.echo("\n=== Solver Statistics ===")
            for solver, count in db_stats['solvers'].items():
                click.echo(f"  {solver}: {count} runs")
        
        if db_stats['gpu_types']:
            click.echo("\n=== GPU Type Statistics ===")
            for gpu, count in db_stats['gpu_types'].items():
                click.echo(f"  {gpu}: {count} runs")
        
        # XML config stats
        try:
            from ..config.xml_manager import get_xml_manager
            xml_manager = get_xml_manager()
            xml_stats = xml_manager.get_stats()
            
            click.echo("\n=== XML Configuration Statistics ===")
            click.echo(f"Total XML configs: {xml_stats.get('total_configs', 0)}")
            click.echo(f"Used configs: {xml_stats.get('used_configs', 0)}")
            click.echo(f"Unused configs: {xml_stats.get('unused_configs', 0)}")
            
            if xml_stats.get('most_used'):
                click.echo("\nMost used XML configs:")
                for config in xml_stats['most_used']:
                    click.echo(f"  {config['name']}: {config['count']} times")
                    
        except Exception as e:
            logger.debug(f"XML stats error: {e}")
        
    except Exception as e:
        click.echo(f"❌ Stats error: {e}")


@cli.command('register-xml')
@click.argument('xml_file', type=click.Path(exists=True))
@click.option('--name', help='Custom name for the XML configuration')
@click.option('--description', help='Description of the XML configuration')
def register_xml(xml_file, name, description):
    """Register an XML configuration file for easy access."""
    try:
        # First, validate the XML file by running it
        click.echo(f"🧪 Testing XML configuration: {xml_file}")
        click.echo("🔄 Running test execution to validate configuration...")
        
        # Run the XML file to check if it works
        try:
            run_result = run_with_xml_config(xml_file)
            if run_result is False:  # If run_with_xml_config returns False, it failed
                click.echo("❌ XML configuration failed validation test. Not registering.")
                return
        except Exception as test_error:
            click.echo(f"❌ XML configuration failed validation test: {test_error}")
            click.echo("❌ Not registering the XML file.")
            return
        
        click.echo("✅ XML configuration passed validation test!")
        
        # Now register the XML file since it passed the test
        from ..config.xml_manager import get_xml_manager
        xml_manager = get_xml_manager()
        
        config_id = xml_manager.register_xml(xml_file, name, description)
        click.echo(f"✅ XML configuration registered with ID: {config_id}")
        click.echo(f"📄 File: {xml_file}")
        click.echo(f"💡 Run with: gsm --run-id {config_id}")
        
    except Exception as e:
        click.echo(f"❌ Error registering XML: {e}")


@cli.command('scan-xml')
@click.argument('directory', type=click.Path(exists=True), default='.')
@click.option('--recursive', '-r', is_flag=True, default=True, help='Scan subdirectories recursively')
def scan_xml(directory, recursive):
    """Scan directory for XML configuration files and register them."""
    try:
        from ..config.xml_manager import get_xml_manager
        xml_manager = get_xml_manager()
        
        added_count = xml_manager.db.scan_directory(directory, recursive)
        if added_count > 0:
            click.echo(f"✅ Found and registered {added_count} XML configurations")
        else:
            click.echo("❌ No new XML configurations found")
            
    except Exception as e:
        click.echo(f"❌ Error scanning for XML files: {e}")


@cli.command('xml-info')
@click.argument('xml_id', type=int)
def xml_info(xml_id):
    """Show detailed information about an XML configuration."""
    try:
        from ..config.xml_manager import get_xml_manager
        xml_manager = get_xml_manager()
        
        config = xml_manager.get_config_info(xml_id)
        if not config:
            click.echo(f"❌ XML configuration ID {xml_id} not found")
            return
        
        click.echo(f"XML Configuration #{config['id']}")
        click.echo("=" * 50)
        click.echo(f"Name: {config['name']}")
        click.echo(f"Description: {config['description']}")
        click.echo(f"File: {config['file_path']}")
        click.echo(f"Work directory: {config['work_dir'] or 'Not specified'}")
        click.echo(f"Binary: {config['binary_path']}")
        click.echo(f"MPI processes: {config['mpi_processes']}")
        click.echo(f"Log file: {config['log_file'] or 'Not specified'}")
        click.echo(f"Created: {config['created_at']}")
        click.echo(f"Last used: {config['last_used'] or 'Never'}")
        click.echo(f"Use count: {config['use_count']}")
        
        if config.get('metadata'):
            metadata = config['metadata']
            if isinstance(metadata, str):
                import json
                try:
                    metadata = json.loads(metadata)
                except:
                    metadata = {}
            
            if metadata.get('sections'):
                click.echo(f"\nXML sections: {', '.join(metadata['sections'])}")
            
            click.echo(f"File size: {metadata.get('file_size', 'Unknown')} bytes")
        
    except Exception as e:
        click.echo(f"❌ Error getting XML info: {e}")


@cli.command('delete-xml')
@click.argument('xml_id', type=int)
@click.option('--force', '-f', is_flag=True, help='Skip confirmation prompt')
def delete_xml(xml_id, force):
    """Delete an XML configuration from the database."""
    try:
        from ..config.xml_manager import get_xml_manager
        xml_manager = get_xml_manager()
        
        # Get config info before deletion
        config = xml_manager.get_config_info(xml_id)
        if not config:
            click.echo(f"❌ XML configuration ID {xml_id} not found")
            return
        
        # Confirmation unless --force is used
        if not force:
            click.echo(f"About to delete XML configuration:")
            click.echo(f"  ID: {config['id']}")
            click.echo(f"  Name: {config['name']}")
            click.echo(f"  File: {config['file_path']}")
            
            confirm = click.confirm("Are you sure you want to delete this configuration?")
            if not confirm:
                click.echo("❌ Deletion cancelled")
                return
        
        success = xml_manager.delete_xml(xml_id)
        if success:
            click.echo(f"✅ XML configuration ID {xml_id} deleted successfully")
        else:
            click.echo(f"❌ Failed to delete XML configuration ID {xml_id}")
            
    except Exception as e:
        click.echo(f"❌ Error deleting XML config: {e}")


@cli.command('update-xml')
@click.argument('xml_id', type=int)
@click.option('--name', help='Update configuration name')
@click.option('--description', help='Update configuration description')
@click.option('--binary-path', help='Update binary path')
@click.option('--mpi-processes', type=int, help='Update number of MPI processes')
@click.option('--log-file', help='Update log file path')
@click.option('--work-dir', help='Update work directory')
def update_xml(xml_id, name, description, binary_path, mpi_processes, log_file, work_dir):
    """Update an existing XML configuration."""
    try:
        from ..config.xml_manager import get_xml_manager
        xml_manager = get_xml_manager()
        
        # Check if config exists
        config = xml_manager.get_config_info(xml_id)
        if not config:
            click.echo(f"❌ XML configuration ID {xml_id} not found")
            return
        
        # Collect updates
        updates = {}
        if name is not None:
            updates['name'] = name
        if description is not None:
            updates['description'] = description
        if binary_path is not None:
            updates['binary_path'] = binary_path
        if mpi_processes is not None:
            updates['mpi_processes'] = mpi_processes
        if log_file is not None:
            updates['log_file'] = log_file
        if work_dir is not None:
            updates['work_dir'] = work_dir
        
        if not updates:
            click.echo("❌ No updates specified. Use --help to see available options.")
            return
        
        # Show what will be updated
        click.echo(f"Updating XML configuration #{xml_id} ({config['name']}):")
        for field, value in updates.items():
            old_value = config.get(field, 'N/A')
            click.echo(f"  {field}: {old_value} -> {value}")
        
        # Perform update
        success = xml_manager.update_xml(xml_id, **updates)
        if success:
            click.echo(f"✅ XML configuration ID {xml_id} updated successfully")
        else:
            click.echo(f"❌ Failed to update XML configuration ID {xml_id}")
            
    except Exception as e:
        click.echo(f"❌ Error updating XML config: {e}")


@cli.command('clear-xml')
@click.option('--force', '-f', is_flag=True, help='Skip confirmation prompt')
def clear_xml(force):
    """Clear all XML configurations from the database."""
    try:
        from ..config.xml_manager import get_xml_manager
        xml_manager = get_xml_manager()
        
        # Get count of configs
        stats = xml_manager.get_stats()
        total_configs = stats.get('total_configs', 0)
        
        if total_configs == 0:
            click.echo("❌ No XML configurations to clear")
            return
        
        # Confirmation unless --force is used
        if not force:
            click.echo(f"About to delete ALL {total_configs} XML configurations from database.")
            click.echo("This action cannot be undone!")
            
            confirm = click.confirm("Are you sure you want to clear all configurations?")
            if not confirm:
                click.echo("❌ Clear operation cancelled")
                return
        
        # Clear all configs
        import sqlite3
        cache_dir = Path.home() / '.cache' / 'gsm'
        db_path = cache_dir / 'xml_configs.db'
        
        with sqlite3.connect(str(db_path)) as conn:
            cursor = conn.execute('DELETE FROM xml_configs')
            deleted_count = cursor.rowcount
        
        click.echo(f"✅ Cleared {deleted_count} XML configurations from database")
            
    except Exception as e:
        click.echo(f"❌ Error clearing XML configs: {e}")


# Hidden/advanced commands group
@cli.group(hidden=True)
def advanced():
    """Advanced commands (hidden from main interface)."""
    pass


def run_with_xml_config(config_file: str):
    """Run solver using XML configuration file (binary execution only)."""
    try:
        import os
        from pathlib import Path
        
        # Check if config file exists (handle both relative and absolute paths)
        config_path = Path(config_file)
        if not config_path.is_absolute():
            config_path = Path.cwd() / config_file
            
        if not config_path.exists():
            click.echo(f"❌ Configuration file not found: {config_path}")
            return
        
        click.echo(f"📂 Configuration file: {config_path}")
        
        # Try extension-based routing first
        try:
            router = get_router()
            info = router.get_framework_info(config_path)
            
            if info['supported'] and info['extension']:
                click.echo(f"🔌 Using extension: {info['extension']} (framework: {info['framework']})")
                
                # Get the extension and delegate to it
                registry = get_registry()
                extension = registry.get_extension(info['extension'])
                
                if extension and hasattr(extension, 'run_binary'):
                    # Extension has a run_binary method - use it for binary execution only
                    try:
                        result = extension.run_binary(config_path)
                        if result:
                            click.echo("✅ Extension-based execution completed successfully!")
                        else:
                            click.echo("❌ Extension-based execution failed")
                        return
                    except Exception as e:
                        click.echo(f"⚠️ Extension execution failed: {e}")
                        click.echo("🔄 Falling back to simple binary execution...")
                
        except Exception as e:
            logger.debug(f"Extension routing failed: {e}")
            click.echo("🔄 Extension system unavailable, using simple binary execution...")
        
        # Simple XML-based binary execution
        run_binary_from_xml(config_path)
            
    except Exception as e:
        click.echo(f"❌ Error running XML config: {e}")


def run_binary_from_xml(config_path: Path):
    """Simple binary execution from XML configuration."""
    try:
        import os
        
        # Parse XML configuration
        import xml.etree.ElementTree as ET
        tree = ET.parse(str(config_path))
        root = tree.getroot()
        
        # Extract essential parameters
        binary_path = None
        work_dir = None
        nproc = 1
        log_file = None
        
        # Extract binary configuration
        binary_section = root.find('.//ParameterList[@name="Binary"]')
        if binary_section is not None:
            for param in binary_section.findall('Parameter'):
                param_name = param.get('name', '')
                param_value = param.get('value', '')
                
                if param_name == 'binary_path':
                    binary_path = param_value
                elif param_name == 'work_dir':
                    work_dir = param_value
                elif param_name == 'mpi_processes':
                    nproc = int(param_value)
        
        # Extract logging configuration
        logging_section = root.find('.//ParameterList[@name="Logging"]')
        if logging_section is not None:
            for param in logging_section.findall('Parameter'):
                param_name = param.get('name', '')
                param_value = param.get('value', '')
                
                if param_name == 'log_file':
                    log_file = param_value
        
        # Fallback to old format for backward compatibility
        if not binary_path or not log_file:
            for param_list in root.findall('.//ParameterList'):
                for param in param_list.findall('Parameter'):
                    param_name = param.get('name', '')
                    param_value = param.get('value', '')
                    
                    if param_name == 'binary_path' and not binary_path:
                        binary_path = param_value
                    elif param_name == 'mpi_processes' and nproc == 1:
                        nproc = int(param_value)
                    elif param_name == 'log_file' and not log_file:
                        log_file = param_value
        
        if not binary_path:
            click.echo("❌ No binary_path specified in XML configuration")
            click.echo("💡 Add <Parameter name=\"binary_path\" type=\"string\" value=\"/path/to/binary\"/>")
            return False
        
        # Handle work directory
        original_cwd = None
        if work_dir:
            work_dir_path = Path(work_dir)
            if not work_dir_path.exists():
                click.echo(f"❌ Work directory not found: {work_dir_path}")
                return False
            
            original_cwd = os.getcwd()
            os.chdir(work_dir)
            click.echo(f"📁 Changed to work directory: {work_dir}")
            
            # If binary_path is relative, resolve it relative to work_dir
            if not Path(binary_path).is_absolute():
                binary_path = str(work_dir_path / binary_path)
        
        try:
            binary_path = Path(binary_path)
            if not binary_path.exists():
                click.echo(f"❌ Binary not found: {binary_path}")
                return False
            
            click.echo(f"🚀 Running binary: {binary_path}")
            click.echo(f"📊 MPI processes: {nproc}")
            
            # Prepare basic command
            base_cmd = [str(binary_path)]
        except Exception as e:
            click.echo(f"❌ Error processing binary path: {e}")
            return False
        
        # Add any extra arguments from XML
        args_section = root.find('.//ParameterList[@name="Binary"]')
        if args_section is not None:
            for param in args_section.findall('Parameter'):
                if param.get('name') == 'arguments':
                    args = param.get('value', '').strip()
                    if args:
                        base_cmd.extend(args.split())
        
        # Only pass config to binary if it's likely to understand it (not for system utilities)
        binary_name = binary_path.name.lower()
        if binary_name not in ['date', 'echo', 'ls', 'cat', 'head', 'tail', 'grep', 'julia']:
            base_cmd.append(f"--config={config_path}")
        
        # Prepare final command with MPI if needed
        if nproc > 1:
            cmd = ["mpirun", "-np", str(nproc)] + base_cmd
        else:
            cmd = base_cmd
        
        try:
            # Set up logging if specified
            if log_file:
                log_path = Path(log_file)
                # Resolve log path relative to work_dir if relative
                if not log_path.is_absolute() and work_dir:
                    log_path = Path(work_dir) / log_file
                
                log_path.parent.mkdir(parents=True, exist_ok=True)
                
                with open(log_path, 'w') as f:
                    result = subprocess.run(cmd, stdout=f, stderr=subprocess.STDOUT, text=True)
                
                click.echo(f"📄 Log saved to: {log_path}")
                
                # Store log in database
                try:
                    from ..database.simple_db import SimpleDatabase
                    config = get_config()
                    db = SimpleDatabase(config.get('database.path'))
                    
                    # Read log content to store in database
                    log_content = ""
                    if log_path.exists():
                        log_content = log_path.read_text()
                        
                    run_id = db.add_test(
                        solver=binary_path.stem,
                        gpu=None,
                        params={
                            'binary_path': str(binary_path), 
                            'config_path': str(config_path),
                            'work_dir': work_dir
                        },
                        log_content=log_content
                    )
                    
                    click.echo(f"💾 Run logged to database (ID: {run_id})")
                except Exception as e:
                    click.echo(f"⚠️ Failed to log to database: {e}")
            else:
                result = subprocess.run(cmd, text=True)
            
            if result.returncode == 0:
                click.echo("✅ Binary execution completed successfully!")
                return True
            else:
                click.echo(f"❌ Binary execution failed with exit code: {result.returncode}")
                return False
                
        finally:
            # Restore original working directory
            if original_cwd:
                os.chdir(original_cwd)
                click.echo(f"📁 Restored working directory: {original_cwd}")
            
    except Exception as e:
        click.echo(f"❌ Error parsing XML or running binary: {e}")
        return False




if __name__ == '__main__':
    cli()